// Copyright (c) 2024 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.


#pragma once

/**
 * @brief   包含了基于LL 库的通用引脚定义和操作
 */

#include "common_cm_pin.hpp"
#include "ioxx_common.hpp"
#include "ioxx_include_device.hpp"  // 引入CMSIS 器件头文件，可能需要用户定义的配置文件


#ifdef STM32F1
    #error "use <cm_f1.hpp> instead"
#endif


#ifndef _IOXX_GPIO_ALTERNATE_SELECTOR_AVAILABLE
    #ifdef LL_GPIO_AF_0

        #define _IOXX_GPIO_ALTERNATE_SELECTOR_AVAILABLE

    #endif
#endif


// 时钟

#define _IOXX_GPIO_ENABLE_CLOCK  _SUPER_CAT3(LL_, _IOXX_GPIO_BUS, _EnableClock)
#define _IOXX_GPIO_DISABLE_CLOCK _SUPER_CAT3(LL_, _IOXX_GPIO_BUS, _DisableClock)


namespace ioxx {

    /**
     * @brief 打开所有GPIO 端口的时钟，AFIO 的时钟也会被打开
     *
     * IOXX 的目标是尽量提供接近Arduino 的开发效率，所以不直接提供太细粒度的控制，
     * 如果在低功耗场合需要精细的考虑外设的时钟，那就使用更底层的工具
     */
    inline void enable_gpio() {
        _IOXX_GPIO_ENABLE_CLOCK(_IOXX_GPIO_ALL_BUS_MASK);
    }

    inline void disable_gpio() {
        _IOXX_GPIO_DISABLE_CLOCK(_IOXX_GPIO_ALL_BUS_MASK);
    }

    // inline void enable_gpio(GPIO_TypeDef *port) {

    // }

    // inline void disable_gpio(GPIO_TypeDef *port) {
    // }
}  // namespace ioxx


// 模式

namespace ioxx {

    enum class mode {
        in = LL_GPIO_MODE_INPUT,
        out = LL_GPIO_MODE_OUTPUT,
        alt = LL_GPIO_MODE_ALTERNATE,
        analog = LL_GPIO_MODE_ANALOG,
    };


    enum class drive {
        push_pull = LL_GPIO_OUTPUT_PUSHPULL,
        open_drain = LL_GPIO_OUTPUT_OPENDRAIN,
    };


    enum class pull {
        up = LL_GPIO_PULL_UP,
        down = LL_GPIO_PULL_DOWN,

#ifdef LL_GPIO_PULL_NO

        none = LL_GPIO_PULL_NO
#else
    // none = LL_GPIO_PULL_UP
#endif
    };


    enum class speed {
        low = LL_GPIO_SPEED_FREQ_LOW,

#ifdef LL_GPIO_SPEED_FREQ_MEDIUM

        medium = LL_GPIO_SPEED_FREQ_MEDIUM,

#else

        medium = low,

#endif

#ifdef LL_GPIO_SPEED_FREQ_HIGH

        high = LL_GPIO_SPEED_FREQ_HIGH,
#else

        high = LL_GPIO_SPEED_FREQ_MEDIUM,
#endif

#ifdef LL_GPIO_SPEED_FREQ_VERY_HIGH

        very_high = LL_GPIO_SPEED_FREQ_VERY_HIGH,
#else

        very_high = high,

#endif

        lowest = low,
        highest = very_high,

        normal = medium,
    };


    // template <typename PinType = _DefaultPinType>
    class PinInit {
       public:
        using PinType = Pin;

       private:
        // 引脚默认配置：输入模式、输出频率最高、推挽输出、无上下拉
        // 上下拉电阻在输出模式下也会生效，会产生不必要的电流，所以默认关闭上下拉
        LL_GPIO_InitTypeDef _param{
            .Pin = 0,
            .Mode = _enum_to_underlying(mode::in),
            .Speed = _enum_to_underlying(speed::normal),
            .OutputType = _enum_to_underlying(drive::push_pull),

#ifdef LL_GPIO_PULL_NO
            .Pull = _enum_to_underlying(pull::none),
#else
            .Pull = 0,
#endif

#ifdef _IOXX_GPIO_AF_SELECTION_AVAILABLE
            .Alternate = 0,
#endif
        };

       public:
        PinInit &mode(ioxx::mode m) {
            _param.Mode = _enum_to_underlying(m);
            return *this;
        }

        PinInit &speed(ioxx::speed s) {
            _param.Speed = _enum_to_underlying(s);
            return *this;
        }

        PinInit &drive(ioxx::drive d) {
            _param.OutputType = _enum_to_underlying(d);
            return *this;
        }

        PinInit &pull(ioxx::pull p) {
            _param.Pull = _enum_to_underlying(p);
            return *this;
        }

#ifdef _IOXX_GPIO_AF_SELECTION_AVAILABLE

        PinInit &af(uint32_t af_select) {
            _param.Alternate = af_select;
            return *this;
        }

#endif

        PinInit &init(const PinType &pin) {
            _param.Pin = pin.pin_mask();
            LL_GPIO_Init(pin.port(), &_param);
            return *this;
        }
    };


    inline void set_pin_mode(const Pin &p, mode m) {
        LL_GPIO_SetPinMode(p.port(), p.pin_mask(), _enum_to_underlying(m));
    }


    inline void set_pin_drive(const Pin &p, drive d) {
        LL_GPIO_SetPinOutputType(p.port(), p.pin_mask(), _enum_to_underlying(d));
    }


    inline void set_pin_speed(const Pin &p, speed d) {
        LL_GPIO_SetPinSpeed(p.port(), p.pin_mask(), _enum_to_underlying(d));
    }


    inline void set_pin_pull(const Pin &p, pull pl) {
        LL_GPIO_SetPinPull(p.port(), p.pin_mask(), _enum_to_underlying(pl));
    }


    /**
     * @brief 用于将引脚在初始的模式和输入模式间切换
     *
     * 软件I2C 等库代码需要让一个引脚分时工作在输入 / 输出模式，
     * 假定用户预先将引脚配置为输出，且输出模式、速度等已经配置好，
     * 可以用InputScope 临时将引脚置为输入模式，读取输入后再恢复为原输出模式
     */
    class InputPinScope {
       public:
        using PinType = Pin;

       private:
        uint32_t _initial_mode;
        uint32_t _initial_pull;

        const Pin &_pin;

       public:
        InputPinScope(const Pin &pin, pull pull_mode) :
            _pin(pin) {
            auto r = pin.port();
            auto p = pin.pin_mask();

            // 对于F1 单片机，引脚模式、输出类型、上下拉并不是用单独
            _initial_mode = LL_GPIO_GetPinMode(r, p);
            _initial_pull = LL_GPIO_GetPinPull(r, p);

            LL_GPIO_SetPinMode(r, p, LL_GPIO_MODE_INPUT);
            LL_GPIO_SetPinPull(r, p, _enum_to_underlying(pull_mode));
        }

        InputPinScope(Pin pin) :
            InputPinScope(pin, pull::up) {}

        ~InputPinScope() {
            auto r = _pin.port();
            auto p = _pin.pin_mask();

            LL_GPIO_SetPinMode(r, p, _initial_mode);
            LL_GPIO_SetPinPull(r, p, _initial_pull);
        }
    };


    using ReadPinScope = InputPinScope;


    /**
     * @brief 用于临时切换输入引脚的上下拉模式，必须提前将引脚置为输入模式
     *
     * 比如用于探测某些模拟电路的状态，引脚默认状态无上下拉，为高阻态，不干扰模拟电路，
     * 探测状态需要临时打开上拉
     */
    class PullScope {
       public:
        using PinType = Pin;

       private:
        uint32_t _initial_pull;

        const Pin &_pin;

       public:
        PullScope(const Pin &pin, pull pull_mode) :
            _pin(pin) {
            auto r = pin.port();
            auto p = pin.pin_mask();

            _initial_pull = LL_GPIO_GetPinPull(r, p);

            LL_GPIO_SetPinPull(r, p, _enum_to_underlying(pull_mode));
        }

        ~PullScope() {
            auto r = _pin.port();
            auto p = _pin.pin_mask();

            LL_GPIO_SetPinPull(r, p, _initial_pull);
        }
    };

}  // namespace ioxx